#include "fs/fs.h"
#include "fs/buf.h"
#include "fs/dir.h"
#include "fs/bitmap.h"
#include "fs/inode.h"
#include "fs/file.h"
#include "mem/vmem.h"
#include "proc/cpu.h"
#include "lib/print.h"
#include "dev/console.h"

// 设备列表(读写接口)
dev_t devlist[N_DEV];

// ftable + 保护它的锁
#define N_FILE 32
file_t ftable[N_FILE];
spinlock_t lk_ftable;

// ftable初始化 + devlist初始化
void file_init()
{
    spinlock_init(&lk_ftable, "ftable");
    for (int i = 0; i < N_FILE; i++) {
        ftable[i].ref = 0;
        ftable[i].type = FD_UNUSED;
    }

    // 初始化设备表
    for (int i = 0; i < N_DEV; i++) {
        devlist[i].read = NULL;
        devlist[i].write = NULL;
    }
    console_init();
}

// alloc file_t in ftable
// 失败则panic
file_t* file_alloc()
{
    file_t *file=NULL;
    spinlock_acquire(&lk_ftable);

    for(int i=0;i<N_FILE;i++)
    {
        if(ftable[i].ref==0)
        {
            file=&ftable[i];
            file->ref=1;
            break;
        }
    }
    spinlock_release(&lk_ftable);
    assert(file!=NULL, "file_alloc: no free file block");
    return file;
}

// 创建设备文件(供proczero创建console)
file_t* file_create_dev(char* path, uint16 major, uint16 minor)
{
    inode_t *ip, *dp;
    char name[DIR_NAME_LEN];
    // 为新文件分配inode
    ip = path_create_inode(path, FT_DEVICE, major, minor);
    if (ip == 0)
        return 0;

    // 添加进目录
    dp = path_to_pinode(path, name);
    inode_lock(dp);
    if (dir_add_entry(dp, ip->inode_num, name) != BLOCK_SIZE)
        goto fail;
    inode_unlock_free(dp);

    // 创建文件
    file_t *f = file_alloc();
    if (f == 0)
    {
        inode_free(ip);
        return 0;
    }
    
    f->ip = ip;
    f->type = FT_DEVICE;
    f->readable = true;
    f->writable = true;
    f->ref = 1;
    f->major = major;
    f->offset = 0;
    return f;
    fail:
        ip->nlink = 0;
        inode_rw(ip, true);
        inode_unlock_free(ip);
        inode_unlock_free(dp);
        return 0;
}

// 打开一个文件
file_t* file_open(char* path, uint32 open_mode)
{
    // 1. 根据路径查找对应的 inode
    inode_t* inode = NULL;
    if (open_mode & MODE_CREATE) { 
        // 如果打开模式中包含创建标志，则尝试创建 inode
        inode = path_create_inode(path, FT_FILE, 0, 0);
        if (inode == NULL) {
            printf("file_open: Failed to create inode for path %s\n", path);
            return NULL; // 创建失败，返回 NULL
        }
    }
    else {
        // 如果不包含创建标志，则只查找文件 inode
        inode = path_to_inode(path);
        if (inode == NULL) {
            printf("file_open: File not found for path %s\n", path);
            return NULL; // 文件不存在，返回 NULL
        }
    }
    file_t *file=file_alloc();
    // 3. 初始化 file_t 结构
    file->ip = inode;               // 绑定到文件的 inode
    file->offset = 0;               // 文件偏移量初始化为 0
    //file->ref = 1;                  // 引用计数已经由 file_alloc 初始化为 1

    // 设置可读可写权限
    file->readable = (open_mode & MODE_READ) != 0;
    file->writable = (open_mode & MODE_WRITE) != 0;

    inode_lock(inode); // 加锁

    // 判断文件类型
    if (inode->type == FT_DIR) {
        file->type = FD_DIR;
    } else if (inode->type == FT_FILE) {
        file->type = FD_FILE;
    } else if (inode->type == FT_DEVICE) {
        file->type = FD_DEVICE;
        file->major = inode->major;

        // 检查设备号是否有效
        if (inode->major < 0 || inode->major >= N_DEV || devlist[inode->major].read == NULL) {
            file_close(file); // 如果无效，关闭文件并释放资源
            inode_unlock(inode); // 解锁
            return NULL;
        }
    } else {
        file_close(file); // 如果类型未知，释放资源
        inode_unlock(inode); // 解锁
        return NULL;
    }
    inode_unlock(inode); // 解锁
    return file;
}

// 释放一个file
void file_close(file_t* file)
{
    if (file == NULL) {
        return; // 如果传入的 file_t 指针为空，直接返回
    }
    spinlock_acquire(&lk_ftable); // 获取文件表锁，保护并发访问

    if (file->ref < 1) {
        panic("file_close: invalid ref count"); // 检查引用计数是否合法
    }

    file->ref--;

    // 如果引用计数不为 0，说明仍有其他地方引用该文件，直接返回
    if (file->ref > 0) {
        spinlock_release(&lk_ftable);
        return;
    }
    // 保存 `file` 的副本以释放锁后操作
    file_t file_copy = *file;
    file->type=FD_UNUSED;
    file->readable = false;
    file->writable = false;
    file->ip = NULL;
    spinlock_release(&lk_ftable); // 释放文件表锁
    // 根据文件类型释放对应资源
    if (file_copy.type == FD_FILE || file_copy.type == FD_DIR) {
        inode_free(file_copy.ip); // 解锁并减少 inode 的引用计数
    } else if (file_copy.type == FD_DEVICE) {
        // 设备文件不需要额外处理资源
        // 如果有设备相关的清理逻辑，可以在此添加
    } else {
        panic("file_close: unknown file type"); // 如果遇到未知类型，触发错误
    }

    return;
}

// 文件内容读取
// 返回读取到的字节数
uint32 file_read(file_t* file, uint32 len, uint64 dst, bool user)
{
    if(file->readable == false) return 0;
    uint32 ret = 0;  // 最终读取的字节数
    switch (file->type) {
        case FD_PIPE:
        case FD_DEVICE:
            // 设备文件读取（需要检查设备号并调用设备的读接口）
            if (file->major >= 0 && file->major < N_DEV && devlist[file->major].read) {
                ret = devlist[file->major].read(len, dst, user);
            } else {
                ret = 0;  // 无效的设备号或未实现读取
            }
            break;

        case FD_FILE:
        case FD_DIR:
            // 普通文件或目录文件读取
            inode_lock(file->ip);  // 加锁，保证并发安全
            ret = inode_read_data(file->ip, file->offset, len, (void *)dst, user);
            file->offset += ret;  // 更新文件偏移量
            inode_unlock(file->ip);  // 解锁
            break;

        default:
            // 未知类型，不支持读取
            ret = 0;
            break;
    }

    return ret;  // 返回成功读取的字节数
}

// 文件内容写入
// 返回写入的字节数
uint32 file_write(file_t* file, uint32 len, uint64 src, bool user)
{

    if (file->writable == false) {
        return 0;
    }
    uint32 ret = 0;  // 最终写入的字节数

    switch (file->type) {
        case FD_DEVICE:
            // 设备文件写入（需要检查设备号并调用设备的写接口）
            if (file->major >= 0 && file->major < N_DEV && devlist[file->major].write) {
                ret = devlist[file->major].write(len, src, user);
            } else {
                ret = 0;  // 无效的设备号或未实现写入
            }
            break;

        case FD_FILE:
            // 普通文件写入
            inode_lock(file->ip);  // 加锁，保证并发安全
            ret = inode_write_data(file->ip, file->offset, len, (void *)src, user);
            file->offset += ret;  // 更新文件偏移量
            inode_unlock(file->ip);  // 解锁
            break;

        default:
            // 未知类型，不支持写入
            ret = 0;
            break;
    }

    return ret;  // 返回成功写入的字节数
}

// flags 可能取值
#define LSEEK_SET 0  // file->offset = offset
#define LSEEK_ADD 1  // file->offset += offset
#define LSEEK_SUB 2  // file->offset -= offset

// 修改file->offset (只针对FD_FILE类型的文件)
uint32 file_lseek(file_t* file, uint32 offset, int flags)
{
    // 只允许操作 FD_FILE 类型的文件
    if (file->type != FD_FILE) {
        printf("file_lseek: type must be FD_FILE, received file id: %d",file->type);
        return -1;  // 非 FD_FILE 类型返回 -1 表示失败
    }

    spinlock_acquire(&lk_ftable);  // 加锁保护 offset 的操作

    switch (flags) {
        case LSEEK_SET:  // 将 offset 设置为指定值
            file->offset = offset;
            break;

        case LSEEK_ADD:  // 将 offset 增加指定值
            file->offset += offset;
            break;

        case LSEEK_SUB:  // 将 offset 减少指定值
            if (file->offset < offset) {  // 防止 offset 变为负值
                file->offset = 0;
            } else {
                file->offset -= offset;
            }
            break;

        default:
            spinlock_release(&lk_ftable);  // 如果 flags 无效，解锁后返回失败
            return -1;
    }

    uint32 new_offset = file->offset;  // 记录新偏移量
    spinlock_release(&lk_ftable);  // 解锁

    return new_offset;  // 返回新的偏移量
}

// file->ref++ with lock
file_t* file_dup(file_t* file)
{
    spinlock_acquire(&lk_ftable);
    assert(file->ref > 0, "file_dup: ref");
    file->ref++;
    spinlock_release(&lk_ftable);
    return file;
}

// 获取文件状态
int file_stat(file_t* file, uint64 addr)
{
    file_state_t state;
    if(file->type == FD_FILE || file->type == FD_DIR)
    {
        inode_lock(file->ip);
        state.type = file->ip->type;
        state.inode_num = file->ip->inode_num;
        state.nlink = file->ip->nlink;
        state.size = file->ip->size;
        inode_unlock(file->ip);

        uvm_copyout(myproc()->pgtbl, addr, (uint64)&state, sizeof(file_state_t));
    }
    return -1;
}